/**
 * @description: 这是Eirudy创建的类~
 * @author: Eirudy
 * @hope: 憧憬成为java大师
 **/

package org.eirudy.shareimage.manager.websocket;

import ch.qos.logback.classic.spi.EventArgUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.NumberSerializers;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.extern.slf4j.Slf4j;
import org.eirudy.shareimage.manager.websocket.model.PictureEditActionEnum;
import org.eirudy.shareimage.manager.websocket.model.PictureEditMessageTypeEnum;
import org.eirudy.shareimage.manager.websocket.model.PictureEditRequestMessage;
import org.eirudy.shareimage.manager.websocket.model.PictureEditResponseMessage;
import org.eirudy.shareimage.model.entity.User;
import org.eirudy.shareimage.service.impl.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 *  图片编辑处理器
 */
@Component
@Slf4j
public class PictureEditHandler extends TextWebSocketHandler {


    // 每张图片的编辑状态
    private final Map<Long,Long> pictureEditingUsers = new ConcurrentHashMap<Long, Long>();

    // 保存所有连接的会话 key: pictureId    value:用户会话集合
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<Long, Set<WebSocketSession>>();

    @Resource
    private UserServiceImpl userServiceImpl;



    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);

        // 保存会话到集合中
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 如果是第一次加入
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        //向所有用户发通知
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("用户 %s 加入编辑",user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userServiceImpl.getUserVO(user));

        // 广播给所有用户
        broadcastToPicture(pictureId, pictureEditResponseMessage);



    }

    /**
     *收到前端发送消息
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        // 获取消息内容，解析pictureEditMessage
        //将json  转化为
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        String type = pictureEditRequestMessage.getType();
        String editAction = pictureEditRequestMessage.getEditAction();

        // 从Session 熟悉中获取到公共参数
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");

        PictureEditMessageTypeEnum pictureEditMessageTypeEnum = PictureEditMessageTypeEnum.getEnumByValue(type);

        switch (pictureEditMessageTypeEnum) {
            case ENTER_EDIT:
                handleEnterEditMessage(pictureEditRequestMessage,session,user,pictureId);
                break;
            case EXIT_EDIT:
                handleExitEditMessage(pictureEditRequestMessage,session,user,pictureId);
                break;
            case EDIT_ACTION:
                handleEditActionMessage(pictureEditRequestMessage,session,user,pictureId);
                break;
            default:
                // 如果是其他操作，返回错误提示
                PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
                pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ERROR.getValue());
                pictureEditResponseMessage.setMessage("消息类型错误");
                pictureEditResponseMessage.setUser(userServiceImpl.getUserVO(user));
                session.sendMessage(new TextMessage(JSONUtil.toJsonStr(pictureEditResponseMessage)));
                break;
        }

    }

    /**
     *  进入编辑操作
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     */
    public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        // 没有用户正在编辑状态，才能进入编辑状态
        if(!pictureEditingUsers.containsKey(pictureId)) {
            // 让pictureEditingUsers 加入
            pictureEditingUsers.put(pictureId,user.getId());

            // 构造相应，返回给其他所有用户
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            String message = String.format("用户 %s 进入编辑",user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userServiceImpl.getUserVO(user));

            // 广播给所有用户
            broadcastToPicture(pictureId, pictureEditResponseMessage);

        }
    }

    /**
     *  处理编辑操作
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     */
    public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        Long editingUserId = pictureEditingUsers.get(pictureId);
        String editAction = pictureEditRequestMessage.getEditAction();
        PictureEditActionEnum actionEnum = PictureEditActionEnum.getEnumByValue(editAction);

        if(actionEnum == null) {
            log.error("无效的编辑操作");
            return;
        }

        // 确认是当前的编辑者
        if(editingUserId != null && editingUserId.equals(user.getId())) {

            // 发送具体的操作通知
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
            String message = String.format("%s 执行了 %s",user.getUserName(),actionEnum.getText());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userServiceImpl.getUserVO(user));

            // 广播给除了当前客户端之外
            broadcastToPicture(pictureId, pictureEditResponseMessage,session);
        }

    }

    /**
     *  退出编辑操作
     * @param pictureEditRequestMessage
     * @param session
     * @param user
     * @param pictureId
     */
    public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        Long editingUserId = pictureEditingUsers.get(pictureId);

        // 确定是当前编辑者
        if(editingUserId != null && editingUserId.equals(user.getId())) {

            pictureEditingUsers.remove(pictureId);

            // 构造相应，返回给其他所有用户
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            String message = String.format("用户 %s 退出编辑",user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setUser(userServiceImpl.getUserVO(user));

            // 广播给所有用户
            broadcastToPicture(pictureId, pictureEditResponseMessage);

        }

    }


    /**
     *  关闭连接
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session,CloseStatus status) throws Exception {
        super.afterConnectionClosed(session,status);
        // 保存会话到集合中
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 移除当前用户编辑状态
        handleExitEditMessage(null,session,user,pictureId);

        // 删除会话,如果 sessionSet 是空，再把这张图片的编辑状态注销掉
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if(sessionSet != null) {
            sessionSet.remove(session);
            if(sessionSet.isEmpty()) {
                pictureSessions.remove(pictureId);
            }
        }

        // 通知其他用户，该用户退出编辑
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("用户 %s 退出编辑",user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userServiceImpl.getUserVO(user));

        // 广播给所有用户
        broadcastToPicture(pictureId, pictureEditResponseMessage);


    }

    /**
     *  广播给该图片的所有用户,支持排除掉某个Session
     * @param pictureId
     * @param pictureEditResponseMessage
     * @param excludeSession 排除掉的Session
     * @throws Exception
     *
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage,WebSocketSession excludeSession) throws Exception {
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if(CollUtil.isNotEmpty(sessionSet)){

            ObjectMapper objectMapper = new ObjectMapper();
            //配置序列化
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(module);

            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);

            for(WebSocketSession session : sessionSet){

                // 排除掉的Session 不发送
                if(excludeSession != null && session.equals(excludeSession)){
                    continue;
                }
                if(session.isOpen()){
                    session.sendMessage(textMessage);
                }
            }
        }

    }

    /**
     *  广播给该图片的所有用户,支持排除掉某个Session
     * @param pictureId
     * @param pictureEditResponseMessage
     * @throws Exception
     *
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws Exception {
        broadcastToPicture(pictureId, pictureEditResponseMessage, null);

    }

}
